﻿#region [ Code Modification History ]

//*******************************************************************************************************
//  ByteBlockReader.cs 
//
//  Code Modification History:
//  -----------------------------------------------------------------------------------------------------
//  15/01/2017 - V. Dennis Kholin
//       Generated original version of source code. 
//*******************************************************************************************************

#endregion

#region [ Using ]

using System;
using System.Collections.Generic;
using System.IO;
using GZipStreamTool.Base.Gzip;

#endregion

namespace GZipStreamTool.Base.Core
{

    #region [ Delegates ]

    /// <summary>
    ///     ByteBlockReaderDelegate
    /// </summary>
    /// <param name="args"></param>
    /// <param name="obj"></param>
    /// <returns></returns>
    public delegate void ByteBlockReaderDelegate(object args, object obj);

    #endregion

    /// <summary>
    ///     ByteBlockReader
    /// </summary>
    public class ByteBlockReader : IDisposable
    {
        /* 
        accepted
        Reading from the RFC 1952 - GZIP :

        Each GZIP file is just a bunch of data chunks (called members), one for each file contained.

        Each member starts with the following bytes:

        0x1F (ID1)
        0x8B (ID2)
        compression method. 0x08 for a DEFLATEd file. 0-7 are reserved values.
        flags. The top three bits are reserved and must be zero.
        (4 bytes) last modified time. May be set to 0.
        extra flags, defined by the compression method.
        operating system, actually the file system. 0=FAT, 3=UNIX, 11=NTFS
        */

        #region [ Members Static ]

        private static readonly int BUFFER_SIZE = 64 * 1024; //64 Kb

        #endregion

        #region [ Members ]

        #endregion

        #region [ Constructors ]

        #endregion

        #region [ Properties ]

        /// <summary>
        ///     SrcStream
        /// </summary>
        public FileStream ReaderFileStream { get; set; }

        #endregion

        #region [ Disposing ]

        /// <summary>
        ///     Dispose
        /// </summary>
        public void Dispose()
        {
            ReaderFileStream.Close();
            ReaderFileStream.Dispose();

            ReaderFileStream?.Dispose();
        }

        #endregion

        #region [ Events ]

        //public event ByteBlockReaderDelegate BlockReadout;
        /// <summary>
        ///     AllBlocksRead
        /// </summary>
        public event ByteBlockReaderDelegate AllBlocksRead;

        #endregion

        #region [ Event Invocation ]

        /// <summary>
        ///     AllBlocksBeenRead
        /// </summary>
        public void AllBlocksBeenRead()
        {
            if (AllBlocksRead != null)
                AllBlocksRead?.Invoke(new EventArgs(), this);
        }

        #endregion

        #region [ Methods ]

        /*
        /// <summary>
        /// ReadGZipBlock
        /// </summary>
        /// <param name="fs"></param>
        /// <returns></returns>
        public static  IEnumerable<GZipBlock> ReadGZipBlock(FileStream fs)
        {
            fs.Position = 0; //reset position
            GZipBlock gzb = new GZipBlock();
            
            GZipBlockHeader gzbh = GZipBlockHeader.InitHeader(fs);
            byte[] b = new byte[1];
            int cyclecounter = 0;
            while (fs.Position < fs.Length)
            {
                if (GZipBlockHeader.IsHeader(ref fs, gzbh) && cyclecounter > 0)
                {
                    yield return gzb;
                    gzb = new GZipBlock();
                };

                fs.Read(b, 0, b.Length);
                gzb.Block.Add(b[0]);
                cyclecounter = cyclecounter + 1;
            }
            yield return gzb;
        }
        */

        #endregion

        #region [ Methods ]

        /*
        /// <summary>
        /// ReadGZipMarked
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ByteBlock> ReadGZipMarked(FileStream fs)
        {
            Dictionary<int, int> gzipmarkers = new Dictionary<int, int>();
            foreach (KeyValuePair<int, int> item in ReadGZipMarkers(fs))
            {
                byte[] data = new byte[item.Value];
                _fs.Position = item.Key;
                _fs.Read(data, 0, data.Length);
                ByteBlock bb = new ByteBlock();
                bb.Block = data;
                yield return bb;
            }
        }
        */


        /// <summary>
        ///     ReagGZipBlock
        /// </summary>
        /// <param name="fs"></param>
        /// <param name="gzbh"></param>
        /// <returns></returns>
        public static IEnumerable<GZipBlock> ReagGZipBlock(FileStream fs, GZipBlockHeader gzbh)
        {
            foreach (var item in ReadGZipBlockMarkers(fs, gzbh))
            {
                var b = new byte[item.Value];
                fs.Position = item.Key;
                fs.Read(b, 0, b.Length);
                var gzb = new GZipBlock();
                gzb.Block = b;
                yield return gzb;
            }
        }

        /// <summary>
        ///     ReadGZipMarkers
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<KeyValuePair<int, int>> ReadGZipBlockMarkers(FileStream fs, GZipBlockHeader gzbh)
        {
            var ID0 = new byte();
            var ID1 = new byte();
            var ID2 = new byte();
            var ID3 = new byte();
            var ID4 = new byte();
            var ID5 = new byte();
            var ID6 = new byte();
            var ID7 = new byte();
            var ID8 = new byte();
            var ID9 = new byte();

            var counter = 0;
            var countercurr = 0;
            var counterprev = 0;
            var headcurr = 0;


            foreach (var bb in Read(fs, BUFFER_SIZE))
                foreach (var b in bb.Block)
                {
                    switch (headcurr)
                    {
                        case 0:
                            ID0 = b;
                            break;
                        case 1:
                            ID1 = b;
                            break;
                        case 2:
                            ID2 = b;
                            break;
                        case 3:
                            ID3 = b;
                            break;
                        case 4:
                            ID4 = b;
                            break;
                        case 5:
                            ID5 = b;
                            break;
                        case 6:
                            ID6 = b;
                            break;
                        case 7:
                            ID7 = b;
                            break;
                        case 8:
                            ID8 = b;
                            break;
                        case 9:
                            ID9 = b;
                            break;

                        default:
                            /*
                                if (ID0 == gzbh.ID0 && ID1 == gzbh.ID1)
                                {
                                    int test = 0;
                                    test = test + 1;
                                } */

                            if (ID0 == gzbh.ID0 &&
                                ID1 == gzbh.ID1 &&
                                ID2 == gzbh.ID2 &&
                                ID3 == gzbh.ID3 &&
                                ID4 == gzbh.ID4 &&
                                ID5 == gzbh.ID5 &&
                                ID6 == gzbh.ID6 &&
                                ID7 == gzbh.ID7 &&
                                ID8 == gzbh.ID8 &&
                                ID9 == gzbh.ID9 && counter > 8)
                            {
                                countercurr = counter - 10;
                                yield return new KeyValuePair<int, int>(counterprev, countercurr - counterprev);
                                counterprev = countercurr;
                            }
                            /*
                               */
                            ID0 = ID1;
                            ID1 = ID2;
                            ID2 = ID3;
                            ID3 = ID4;
                            ID4 = ID5;
                            ID5 = ID6;
                            ID6 = ID7;
                            ID7 = ID8;
                            ID8 = ID9;
                            ID9 = b;

                            break;
                    }
                    headcurr = headcurr + 1;


                    counter = counter + 1;
                }

            if (countercurr == 0)
                yield return new KeyValuePair<int, int>(countercurr, counter);
            else
                yield return new KeyValuePair<int, int>(counterprev, counter - counterprev);
        }

        /// <summary>
        ///     ReadByte
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ByteBlock> Read(FileStream fs)
        {
            foreach (var bb in Read(fs, BUFFER_SIZE))
                yield return bb;
        }


        /// <summary>
        ///     Read
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<byte> ReadByte(FileStream fs)
        {
            foreach (var bb in Read(fs, BUFFER_SIZE))
                foreach (var b in bb.Block)
                    yield return b;
        }


        /// <summary>
        ///     Read
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ByteBlock> Read(FileStream fs, int byteBlockSize)
        {
            long rangeByteBgn = 0;
            var rangeByteEnd = fs.Length;
            long blockIndex = 0;

            while (rangeByteEnd > rangeByteBgn)
            {
                var bb = new ByteBlock(byteBlockSize);

                if (rangeByteBgn + bb.Length > rangeByteEnd)
                {
                    byteBlockSize = (int)(rangeByteEnd - rangeByteBgn);
                    bb.Block = new byte[rangeByteEnd - rangeByteBgn];
                }

                fs.Position = rangeByteBgn;
                fs.Read(bb.Block, 0, (int)bb.Length);
                bb.ID = blockIndex;
                rangeByteBgn = fs.Position;

                blockIndex = blockIndex + 1;
                yield return bb;
            }
            fs.Position = 0;
            //   AllBlocksBeenRead();
        }

        #endregion
    }
}